﻿
namespace Patterns4Net.ArchitectureExplorer.ViewModels.Services
{
    using System;
    using System.IO;
    using System.Text.RegularExpressions;
    using System.Xml;
    using System.Linq;
    using System.ComponentModel.Composition;
    using System.Collections.Generic;
    using System.Xml.Linq;
    using System.Xml.XPath;

    /// <summary>
    /// Provides default implementation of <see cref="IVisualStudioFilesParser"/>.
    /// </summary>
    [Export(typeof(IVisualStudioFilesParser))]
    public class VisualStudioFilesParser : IVisualStudioFilesParser
    {
        public string GetAssemblyFilename(string projectFilename)
        {
            XDocument project;
            try
            {
                project = XDocument.Load(projectFilename);
            }
            catch (Exception ex)
            {
                throw new VisualStudioParserException("Could not load project filename " + projectFilename, ex);
            }

            return GetAssemblyFilename(projectFilename, project, File.Exists, File.GetLastWriteTime);
        }

        public IEnumerable<string> GetProjects(string solutionFilename)
        {
            string content;
            try
            {
                content = File.ReadAllText(solutionFilename);
            }
            catch (IOException ex)
            {
                throw new VisualStudioParserException("Solution " + solutionFilename + " cannot be read.", ex);
            }

            return this.GetProjectFilenames(solutionFilename, content);
        }

        internal string GetAssemblyFilename(
            string projectFilename, 
            XDocument project,
            Func<string, bool> fileExists,
            Func<string, DateTime> lastWrite)
        {
            var nsManager = new XmlNamespaceManager(new NameTable());
            nsManager.AddNamespace("msbuild", "http://schemas.microsoft.com/developer/msbuild/2003");

            var assembly = project.XPathSelectElement("//msbuild:AssemblyName", nsManager);
            if (assembly == null)
            {
                throw new VisualStudioParserException("Element 'AssemblyName' not found in file " + projectFilename);
            }

            var outputPaths = project.XPathSelectElements("//msbuild:OutputPath", nsManager);
            if (outputPaths == null)
            {
                throw new VisualStudioParserException("Element 'OutputPath' not found in file " + projectFilename);
            }

            var projectDirectory = Path.GetDirectoryName(projectFilename);
            var fileNames = outputPaths.Select(x => x.Value)
                .SelectMany(path => new[]
                                        {
                                            Path.Combine(projectDirectory, path, assembly.Value + ".exe"),
                                            Path.Combine(projectDirectory, path, assembly.Value + ".dll")
                                        });
            var file = fileNames.Where(fileExists)
                .OrderByDescending(lastWrite)
                .FirstOrDefault();

            if (file == null)
            {
                throw new AssemblyFileNotFound(
                    string.Format(
                        "Assembly file for project {0} was not found. " +
                        "It may happen whe the project was not build, try building the project in Visual Studio." +
                        "Searched paths: {1}.",
                        projectFilename,
                        string.Join(";", fileNames)));
            }

            return file;
        }

        internal IEnumerable<string> GetProjectFilenames(
            string solutionFilename, 
            string solutionContent)
        {
            var projectsEntries =
                Regex.Matches(solutionContent, @"Project(?<projectdata>(.|\n)*?)EndProject")
                    .Cast<Match>()
                    .Select(r => r.Result("${projectdata}"))
                    .Where(r => r != null)
                    .Select(r => r.Split(','));
            var solutionPath = Path.GetDirectoryName(solutionFilename);
            foreach (var projectsEntry in projectsEntries)
            {
                if (projectsEntry.Length < 2)
                {
                    throw new VisualStudioParserException(
                        string.Format(
                            "Entry of project {0} in solution {1} has not enough comma separated items",
                            projectsEntry,
                            solutionFilename));
                }

                var project = projectsEntry.Skip(1).First().Trim('"', ' ', '\t', '\n');
                yield return Path.Combine(solutionPath, project);
            }            
        }
    }

    public class AssemblyFileNotFound : VisualStudioParserException
    {
        public AssemblyFileNotFound(string message) 
            : base(message, null)
        {
        }
    }
}
